# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


# TODO(epg): Probably some of these are stale...

# TODO(epg): And of course all str here must be unicode, and
# gvn.cmdline.main needs to get the Context itself and encode before
# printing error messages.

import os
import platform

import svn.core


#: list of SubversionException.apr_err values which mean "out of date"
SVN_OUT_OF_DATE = [svn.core.SVN_ERR_FS_TXN_OUT_OF_DATE,
                   # different code with ra-dav, oddly enough
                   svn.core.SVN_ERR_FS_CONFLICT]


class Root(Exception):
  """Root of error class hierarchy

  Attributes:
  code          -- sys.exit code to use if caller considers the error fatal
  """
  code = None


class User(Root):
  """User errors are everything else, and not necessarily the user's
  fault: connection refused, auth errors, bad gvn command-line
  parameters, and so on.
  """
  diag_message = property(lambda self: str(self),
                       doc="""error message to use in debug/diagnostic mode""")


class BadOptions(User):
  """Exception to raise if options, or arguments to options, are invalid,
  inappropriate, missing, misused, abused, or subject to corporate bribery.
  """
  code = 2


class BadOperands(User):
  """Exception to raise if an operand is similarly troublesome (see
  GvnOptions's docstring).
  """
  code = 3


class InvalidChangeName(User):
  code = 4


class NotWC(User):
  code = 5
  def __init__(self, path):
    User.__init__(self, "'%s' is not a working copy" % (path,))


class Editor(User):
  code = 6
  def __init__(self, cmd, status):
    User.__init__(self, platform.DescribeSubprocessError(cmd, status))


class Unmodified(User):
  code = 7
  def __init__(self, status):
    User.__init__(self, 'not modified: ' + status)


class Conflict(User):
  code = 8
  def __init__(self, path):
    self.path = path
  def __str__(self):
    return "'%s' remains in conflict" % (self.path,)


class RepoPath(User):
  code = 9
  def __init__(self, path, revision):
    User.__init__(self,
                       "URL '%s' non-existent in revision %d" % (path,
                                                                 revision))

class NoProject(User):
  code = 10
  def __init__(self, project):
    User.__init__(self, 'No project for ' + project)


class ChangeIsPath(User):
  code = 11
  def __init__(self, name):
    User.__init__(self,
                "Change '%s' is a pathname; use '--force-change' to override"""
                % (name,))
    self.name = name


class NoMailRecipients(User):
  code = 12
  def __init__(self):
    User.__init__(self,
               "Must specify recipients on command line or in the email form")


class NoUserChangeBranch(User):
  code = 13
  def __init__(self, user_path):
    User.__init__(self, ("User changebranch path '//%s' does not exist."
                         % (user_path,)))


class InvalidProjectName(User):
  code = 14
  def __init__(self, project):
    User.__init__(self, 'Project "' + project +
                        '" is not a valid project name.')


class NoChangeBranchBase(User):
  code = 15
  def __init__(self, base_path):
    User.__init__(self, ("project changebranch base '//%s' does not exist."
                         % (base_path,)))


class MixedPaths(User):
  code = 16
  def __init__(self):
    User.__init__(self, 'All must be absolute or relative')


class NoDiffCommand(User):
  code = 17
  def __init__(self):
    User.__init__(self,
        'No diff command found; see http://code.google.com/p/gvn/wiki/Install')

class OutOfDateParent(User):
  code = 18
  def __init__(self, svn_error, branch_path, revision, change_path):
    self.svn_error = svn_error
    self.branch_path = branch_path
    self.revision = revision
    self.change_path = change_path
  diag_message = property(lambda self: 'svn: %s\n'
                          'Tried to branch from %s@%d\n'
                          "but %s doesn't exist in that revision"
                          % (self.svn_error.args[0], self.branch_path,
                             self.revision, self.change_path))
  def __str__(self):
    return ("'%s/' is newer than '%s/'; try gvn update '%s'"
            % (self.change_path, self.branch_path, self.branch_path))


class BranchFromRoot(User):
  code = 19
  def __init__(self):
    User.__init__(self, 'Cannot branch from repository root')


class Mail(User):
  code = 20


class Cmdline(User):
  pass


class BadCommand(Cmdline):
  code = 21
  def __init__(self, command):
    self.command = command
  def __str__(self):
    return "Unknown command: '%s'" % (self.command,)


# TODO(epg): Use these two instead of BadOptions and BadOperands above.
# class BadOperands(Cmdline):
#   code = 22
#   def __init__(self, operands, message='Not enough arguments provided'):
#     self.operands = operands
#     self.message = message
#   def __str__(self):
#     return self.message % {'operands': operands}
# class BadOption(Cmdline):
#   code = 23
#   def __init__(self, option, message='invalid option: %(option)s'):
#     self.option = option
#     self.message = message
#   def __str__(self):
#     return self.message % {'option': self.option}


class UnknownEncoding(User):
  code = 24
  def __init__(self, encoding):
    self.encoding = encoding
  def __str__(self):
    return 'unknown encoding: %s' % (self.encoding,)


class Encoding(User):
  code = 25
  def __init__(self, unicode_error):
    self.unicode_error = unicode_error
  def __str__(self):
    e = self.unicode_error
    if isinstance(e, UnicodeDecodeError):
      action = 'decode bytes'
    elif isinstance(e, UnicodeEncodeError):
      action = 'encode character'
    else:
      # Shouldn't be called with anything else, e.g. UnicodeTranslateError.
      return str(e)
    # Get everything up to but not including the bogon.
    preceding = e.object[:e.start]
    # Get the column number of the line of the bogon.
    column = e.start - preceding.rindex('\n')
    # Get the line number of the bogon; number of newlines + 1 because
    # preceding doesn't have the final newline (if any).
    line = preceding.count('\n') + 1
    # Get no more than 40 bytes of the line containing the bogon (or
    # the preceding line if the bogon started the line).
    preceding = preceding[-40:].splitlines()[-1]
    return ("'%s' codec can't %s at column %d of line %d; preceding text:\n%s"
            % (e.encoding, action, column, line, preceding))


class Internal(Root):
  """Internal errors are caused by gvn bugs: improper use of svn (or
  an svn binding), one layer of gvn misusing another, and so on.
  """
  code = 100


# XXX I think we have multiple errors here, and maybe some are
# internal and some are User; i haven't looked yet.
class ChangeBranch(Internal):
  code = 105


class PathNotChild(Internal):
  code = 106
  def __init__(self, child, parent):
    Internal.__init__(self,
                           "'%s' not a child of '%s'" % (child, parent))


class WCReOpen(Internal):
  code = 107
  def __init__(self, write_lock, requested_write_lock,
               recursive, requested_recursive):
    Internal.__init__(self,
                           "WorkingCopy.Open mismatched write_lock (%s vs. %s) (%s vs. %s)"
                           % (write_lock, requested_write_lock, recursive, requested_recursive))


class WCClosed(Internal):
  code = 108


class NotShortURL(Internal):
  code = 109
  def __init__(self, url):
    Internal.__init__(self, "'%s' is not a short URL." % (url,))

class NotChangeBranched(Internal):
  code = 110
  def __init__(self, path):
    Internal.__init__(self, "'%s' not changebranched" % (path,))
    self.path = path
